/*
  ==============================================================================

   Demonstration "Hello World" application in JUCE
   Copyright 2004 by Julian Storer.

  ==============================================================================
*/

#include "../../juce/juce_amalgamated.h"
#include "MyWaveformComponent.h"

//==============================================================================
class AudioDemo  :  public Component,
					public FilenameComponentListener,
					public ButtonListener,
					public ChangeListener,
					public AudioIODeviceCallback,
					public SliderListener,
					public MyWaveformEditorListener
{
    //==============================================================================
    FilenameComponent* fileChooser;
    TextButton* playButton;
    TextButton* stopButton;
    TextButton* audioSettingsButton;
	Slider* levelSlider;
	Slider* horizontalZoomSlider;
	Slider* verticalZoomSlider;
	Slider* horizontalOffsetSlider;
	MyWaveformEditor* waveform;
			
    //==============================================================================
    // this wraps the actual audio device
    AudioDeviceManager audioDeviceManager;
	
    // this allows an audio source to be streamed to the IO device
    AudioSourcePlayer audioSourcePlayer;
	
    // this controls the playback of a positionable audio stream, handling the
    // starting/stopping and sample-rate conversion
    AudioTransportSource transportSource;
		
    // this source is used to mix together the output from our synth source
    // and wave player source
    MixerAudioSource mixerSource;
	
    // this is the actual stream that's going to read from the audio file.
    AudioFormatReaderSource* currentAudioFileSource;
	
    File currentFile;
	double audioLevel;
	double smoothAudioLevel;
	bool stopping;

public:
	//==============================================================================
	AudioDemo()
    {
		setName (T("Audio"));
		
		currentAudioFileSource = 0;
		
		//==============================================================================
		AudioFormatManager formatManager;
		formatManager.registerBasicFormats();
		
		addAndMakeVisible (fileChooser = new FilenameComponent (T("audiofile"),
																File::nonexistent,
																true, false, false,
																formatManager.getWildcardForAllFormats(),
																String::empty,
																T("(choose a WAV or AIFF file to play)")));
		fileChooser->addListener (this);
		fileChooser->setBrowseButtonText (T("browse"));
		
		addAndMakeVisible (playButton = new TextButton (T("play"),
														T("click here to play the current audio file")));
		playButton->addButtonListener (this);
		playButton->setColour (TextButton::buttonColourId, Colours::lightgreen);
		playButton->setColour (TextButton::buttonOnColourId, Colours::lightgreen);
		playButton->setConnectedEdges (Button::ConnectedOnRight);
		
		addAndMakeVisible (stopButton = new TextButton (T("stop"),
														T("click here to play the current audio file")));
		stopButton->addButtonListener (this);
		stopButton->setColour (TextButton::buttonColourId, Colours::red);
		stopButton->setColour (TextButton::buttonOnColourId, Colours::red);
		stopButton->setConnectedEdges (Button::ConnectedOnLeft);
		
		addAndMakeVisible (levelSlider = new Slider (T("Level Slider")));
		levelSlider->setTooltip(T("Changle the level of audio playback"));
		
		levelSlider->setRange(0.0, 1.0);
		levelSlider->setValue(0.5);
		levelSlider->addListener(this);
		
		smoothAudioLevel = audioLevel = 0.0;
		
		addAndMakeVisible (audioSettingsButton = new TextButton (T("show audio settings..."),
																 T("click here to change the audio device settings")));
		audioSettingsButton->addButtonListener (this);
		
		addAndMakeVisible(horizontalZoomSlider = new Slider(T("Horizontal Zoom Slider")));
		horizontalZoomSlider->setRange(0.0001, 512.0);//, 1.0);
		horizontalZoomSlider->setSkewFactorFromMidPoint(1.0);
		horizontalZoomSlider->setValue(128.0);
		horizontalZoomSlider->addListener(this);
		
		addAndMakeVisible(horizontalOffsetSlider = new Slider(T("Horizontal Offset Slider")));
		horizontalOffsetSlider->setRange(0.0, 0.0);//, 1.0);
		horizontalOffsetSlider->setValue(0.0);
		horizontalOffsetSlider->addListener(this);
		
		addAndMakeVisible(verticalZoomSlider = new Slider(T("Vertical Zoom Slider")));
		verticalZoomSlider->setRange(0.1, 10.0);
		verticalZoomSlider->setSkewFactorFromMidPoint(1.0);
		verticalZoomSlider->setValue(1.0);
		verticalZoomSlider->addListener(this);
		
		addAndMakeVisible(waveform = new MyWaveformEditor(NormalAndRoundBlocky,
														  true,		//left ruler
														  false,		//right ruler
														  true,		//above ruler
														  false));	//below ruler
		
		waveform->addListener(this);
		
		//==============================================================================
		// register for start/stop messages from the transport source..
		transportSource.addChangeListener (this);
		
		// and initialise the device manager with no settings so that it picks a
		// default device to use.
		const String error (audioDeviceManager.initialise (1, /* number of input channels */
			2, /* number of output channels */
			0, /* no XML settings.. */
			true  /* select default device on failure */));

		if (error.isNotEmpty())
		{
			AlertWindow::showMessageBox (AlertWindow::WarningIcon,
										 T("Audio Demo"),
										 T("Couldn't open an output device!\n\n") + error);
		}
		else
		{
			// add the audio source to our mixer..
			mixerSource.addInputSource (&transportSource, false);
			
			// ..and connect the mixer to our source player.
			audioSourcePlayer.setSource (&mixerSource);
			
			// start the IO device pulling its data from our callback..
			audioDeviceManager.setAudioCallback (this);
		}
	}

	~AudioDemo()
	{
		audioDeviceManager.setAudioCallback (0);
		
		transportSource.removeChangeListener (this);
		waveform->removeListener(this);
		
		transportSource.setSource (0);
		deleteAndZero (currentAudioFileSource);
		
		audioSourcePlayer.setSource (0);
		
		deleteAllChildren();
	}

	//==============================================================================
	void audioDeviceIOCallback (const float** inputChannelData,
								int totalNumInputChannels,
								float** outputChannelData,
								int totalNumOutputChannels,
								int numSamples)
	{
		// smooth out rapid level changes
		smoothAudioLevel = (audioLevel - smoothAudioLevel) * (64.0/numSamples) + smoothAudioLevel;
		
		// pass the audio callback on to our player source
		audioSourcePlayer.audioDeviceIOCallback (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples);
		
		for(int i = 0; i < totalNumOutputChannels; i++) {
            float *outputData = outputChannelData[i];
			if (outputData != 0) {
                for(int j = 0; j < numSamples; j++) {
					outputData[j] *= smoothAudioLevel;
				}
			}
		}
		
		if(stopping == true && smoothAudioLevel < 0.01) {
			transportSource.stop();
			stopping = false;
		}
	}

	void audioDeviceAboutToStart (AudioIODevice* device)
	{
		audioSourcePlayer.audioDeviceAboutToStart (device);
	}

	void audioDeviceStopped()
	{
		audioSourcePlayer.audioDeviceStopped();
	}

	//==============================================================================
	void paint (Graphics& g)
	{
		// print some text to explain what state we're in.
		
		g.setColour (Colours::black);
		g.setFont (14.0f);
		
		String s;
		
		if(stopping == true)
			s = T("stopping");
		else if (transportSource.isPlaying())
			s = T("playing");
		else
			s = T("stopped");
		
		if (currentAudioFileSource == 0)
			s += T(" - no source file selected");
		else
			s += T(" - file: \"") + currentFile.getFullPathName() + T("\"");
		
		g.drawText (s, 10, 85, getWidth() - 10, 24, Justification::centredLeft, true);
	}

	void resized()
	{
		fileChooser->setBounds (10, 10, getWidth() - 20, 24);
		playButton->setBounds (10, 50, 100, 24);
		stopButton->setBounds (110, 50, 100, 24);
		levelSlider->setBounds(250, 50, 300, 24);
		audioSettingsButton->setBounds (10, 120, 200, 24);
		audioSettingsButton->changeWidthToFitText();
		
		horizontalZoomSlider->setBounds(240, 120, 180, 24);
		horizontalOffsetSlider->setBounds(430, 120, 180, 24);
		verticalZoomSlider->setBounds(620, 120, 180, 24);
		waveform->setBounds(50, 180, 800, 200);
		
		updateButtons();
	}

	void updateButtons()
	{
		playButton->setEnabled (currentAudioFileSource != 0 && ! transportSource.isPlaying());
		stopButton->setEnabled (transportSource.isPlaying());
		repaint();
	}

	void buttonClicked (Button* button)
	{
		if (button == playButton)
		{
			transportSource.setPosition (0.0);
			transportSource.start();
			audioLevel = levelSlider->getValue();
			stopping = false;
		}
		else if (button == stopButton)
		{
			stopping = true;
			audioLevel = 0.0;
			//transportSource.stop();
			repaint();
		}
		else if (button == audioSettingsButton)
		{
			// Create an AudioDeviceSelectorComponent which contains the audio choice widgets...
			
			AudioDeviceSelectorComponent audioSettingsComp (audioDeviceManager,
															0, 1,
															2, 2,
															true,
															false,
															true,
															false);
			
			// ...and show it in a DialogWindow...
			audioSettingsComp.setSize (500, 400);
			
			DialogWindow::showModalDialog (T("Audio Settings"),
										   &audioSettingsComp,
										   this,
										   Colours::azure,
										   true);
		}
	}

	void sliderValueChanged(Slider* whichSlider)
	{
		if(whichSlider == levelSlider) {
			if(transportSource.isPlaying())
				audioLevel = levelSlider->getValue();
		}
		else if(whichSlider == horizontalZoomSlider) {
			waveform->setSamplesPerPixel(horizontalZoomSlider->getValue());
		}
		else if(whichSlider == horizontalOffsetSlider) {
			waveform->setSamplesOffset(horizontalOffsetSlider->getValue());
		}
		else if(whichSlider == verticalZoomSlider) {
			waveform->setVerticalZoom(verticalZoomSlider->getValue());
		}
	}

	void filenameComponentChanged (FilenameComponent*)
	{
		// this is called when the user changes the filename in the file chooser box
		
		File audioFile (fileChooser->getCurrentFile());
		
		// unload the previous file source and delete it..
		transportSource.stop();
		transportSource.setSource (0);
		deleteAndZero (currentAudioFileSource);
		
		// create a new file source from the file..
		
		// get a format manager and set it up with the basic types (wav and aiff).
		AudioFormatManager formatManager;
		formatManager.registerBasicFormats();
		
		AudioFormatReader* reader = formatManager.createReaderFor (audioFile);
		
		waveform->setReader(reader); // do this even if reader is null 
									 // and the waveform classes should take care of deleting the reader
		
		if (reader != 0)
		{			
			horizontalOffsetSlider->setRange(0, reader->lengthInSamples);//, 1);
			
			currentFile = audioFile;
			
			currentAudioFileSource = new AudioFormatReaderSource (reader, false); // don't let AudioFormatReaderSource delete!
			
			// ..and plug it into our transport source
			transportSource.setSource (currentAudioFileSource,
									   32768, // tells it to buffer this many samples ahead
									   reader->sampleRate);
			
			StringPairArray meta = reader->metadataValues;
			if(meta.size()>0) {
				StringArray keys = meta.getAllKeys();
				for(int i = 0; i < keys.size(); i++) {
					String key = keys[i];
					//printf("Metadata: %s - %s\n", (const char*)key, (const char*)meta[key]);
				}
			} else {
				//printf("no meta data\n");
			}
		}
		
		updateButtons();
	}

	void changeListenerCallback (void*)
	{
		// callback from the transport source to tell us that play has
		// started or stopped, so update our buttons..
		updateButtons();
	}

    void samplesPerPixelChanged (MyWaveformMultiDisplay* waveform)
	{
		//printf("AudioDemo::samplesPerPixelChanged(%p)\n", waveform);
	}

	void samplesOffsetChanged(MyWaveformMultiDisplay* waveform)
	{
		//printf("AudioDemo::samplesOffsetChanged(%p)\n", waveform);
	}

	void verticalZoomChanged(MyWaveformMultiDisplay* waveform)
	{
		//printf("AudioDemo::verticalZoomChanged(%p)\n", waveform);
	}

	void selectionChanged(MyWaveformSelection * selection)
	{
		//printf("AudioDemo::selectionChanged(%p)\n", selection);
	}

};



//==============================================================================
/** This is the top-level window that we'll pop up. Inside it, we'll create and
    show a HelloWorldContentComponent component.
*/
class HelloWorldWindow  : public DocumentWindow
{
public:
    //==============================================================================
    HelloWorldWindow()
        : DocumentWindow (T("Hello World"),
                          Colours::lightgrey, 
                          DocumentWindow::allButtons, 
                          true)
    {
        //setContentComponent (new HelloWorldContentComponent());
		setContentComponent (new AudioDemo());

        setVisible (true);
		setResizable(true,true);

        // centre the window on the desktop with this size
        centreWithSize (900, 450);
    }

    ~HelloWorldWindow()
    {
        // (the content component will be deleted automatically, so no need to do it here)
    }

    //==============================================================================
    void closeButtonPressed()
    {
        // When the user presses the close button, we'll tell the app to quit. This 
        // window will be deleted by the app object as it closes down.
        JUCEApplication::quit();
    }
};


//==============================================================================
/** This is the application object that is started up when Juce starts. It handles
    the initialisation and shutdown of the whole application.
*/
class JUCEHelloWorldApplication : public JUCEApplication
{
    /* Important! NEVER embed objects directly inside your JUCEApplication class! Use
       ONLY pointers to objects, which you should create during the initialise() method
       (NOT in the constructor!) and delete in the shutdown() method (NOT in the
       destructor!)

       This is because the application object gets created before Juce has been properly
       initialised, so any embedded objects would also get constructed too soon.
   */
    HelloWorldWindow* helloWorldWindow;

public:
    //==============================================================================
    JUCEHelloWorldApplication()
        : helloWorldWindow (0)
    {
        // NEVER do anything in here that could involve any Juce function being called
        // - leave all your startup tasks until the initialise() method.
    }

    ~JUCEHelloWorldApplication()
    {
        // Your shutdown() method should already have done all the things necessary to
        // clean up this app object, so you should never need to put anything in
        // the destructor.

        // Making any Juce calls in here could be very dangerous...
    }

    //==============================================================================
    void initialise (const String& commandLine)
    {
        // just create the main window...
        helloWorldWindow = new HelloWorldWindow();

        /*  ..and now return, which will fall into to the main event
            dispatch loop, and this will run until something calls
            JUCEAppliction::quit().

            In this case, JUCEAppliction::quit() will be called by the
            hello world window being clicked.
        */
    }

    void shutdown()
    {
        // clear up..

        if (helloWorldWindow != 0)
            delete helloWorldWindow;
    }

    //==============================================================================
    const String getApplicationName()
    {
        return T("Hello World for JUCE");
    }

    const String getApplicationVersion()
    {
        return T("1.0");
    }

    bool moreThanOneInstanceAllowed()
    {
        return true;
    }

    void anotherInstanceStarted (const String& commandLine)
    {
    }
};


//==============================================================================
// This macro creates the application's main() function..
START_JUCE_APPLICATION (JUCEHelloWorldApplication)
